﻿using System;
using Validity.Localization;

namespace Validity.Rules
{
  public interface IComparisonRule<TObject, TValue>
    : IRule<TObject, TValue>
  {
    Boolean IsViolatedBy(TObject obj, TValue value, Comparison<TValue> comparer);
  }

  public class ComparisonRule<TObject, TValue>
    : IComparisonRule<TObject, TValue>
    where TValue : IComparable
  {
    private readonly Func<TObject, TValue> _valueRetriever;
    private readonly Predicate<Int32> _resultEvaluator;
    private readonly Func<TObject, TValue, String> _messageFactory;

    public ComparisonRule(Func<TObject, TValue> valueRetriever, Predicate<Int32> resultEvaluator, Func<TObject, TValue, String> messageFactory)
    {
      Verify.NotNull(valueRetriever);
      Verify.NotNull(resultEvaluator);
      Verify.NotNull(messageFactory);

      _valueRetriever = valueRetriever;
      _resultEvaluator = resultEvaluator;
      _messageFactory = messageFactory;
    }

    protected virtual Int32 DefaultComparison(TValue lhs, TValue rhs)
    {
      if (lhs == null && rhs == null)
        return 0;

      if (lhs == null)
        return -1;

      return lhs.CompareTo(rhs);
    }

    public Boolean IsViolatedBy(TObject obj, TValue value)
    {
      return IsViolatedBy(obj, value, DefaultComparison);
    }

    public Boolean IsViolatedBy(TObject obj, TValue value, Comparison<TValue> comparer)
    {
      Verify.NotNull(comparer);

      TValue lhs = value;
      TValue rhs = _valueRetriever.Invoke(obj);

      return !_resultEvaluator.Invoke(comparer.Invoke(lhs, rhs));
    }

    public RuleViolation GetRuleViolation(TObject obj, TValue value)
    {
      return new RuleViolation(_messageFactory.Invoke(obj, value) ?? RuleViolations.General);
    }
  }
}
